2023-03-05 IPSec VPN zwischen LANCOM-Router und OpenBSD
IPSec IKEv2 VPN zwischen LANCOM (LCOS) und OpenBSD (OpenIKED)
Versionen zum Schreibzeitpunkt: LCOS 10.72.0092SU2, OpenBSD 7.2 GENERIC#7
In dem Beispiel hier geht es um ein sogenanntes “RoadWarrior Setup” oder auch einen “Einwahl-Zugang”. (Mit OpenBSD-Host als Client.) Es geht mir weder um das Routen von Client-Traffic über ein OpenBSD-Gateway, noch um das Umleiten des kompletten Internetverkehrs. Da ich dafür keinen persönlichen Bedarf habe, wollte ich die bei einem kurzen Test auftretenden Probleme nicht debuggen. (Was nicht heißt das es nicht geht!)
Hier geht es um das Einbinden entfernter Server ins lokale LAN und den Zugriff per Laptop aufs Heimnetz.
Authentifizierung mit Pre-Shared Key (PSK)
Auf Router Verbindung anlegen, wie man die halt so anlegt.
Auf OpenBSD:
# cat /etc/iked.conf
set dpd_check_interval 30
ikev2 "Lancom" active \
from 192.168.0.0/24 to any \
from dynamic to 192.168.0.0/24 \
peer v4.vpn.example.com \
childsa enc aes-256-gcm group modp2048 \ [1]
srcid Open@BSD \
ikelifetime 54000 \
lifetime 14400 bytes 1G \
psk "f00b4r" \
request address any \
iface lo1
#eof
“set dpd_check_interval 30” ist komplett optional. Die Dead Peer Detection auf 30 Sekunden gesetzt entspricht dem LANCOM-Standard. So haben beide Seiten denselben Wert.
“from 192.168.0.0/24 to any \” erlaubt es dem VPN, den von der LANCOM-Seite kommenden 192.168.0.0/24-Verkehr in die OpenBSD-Maschine zu lassen. Es ist auch möglich den Verkehr auf bestimmte Protokolle und Ports einzuschränken. Siehe
⎇manpage für Details.
“from dynamic to 192.168.0.0/24 \” Welche IP-Adresse auch immer (dynamic) der OpenBSD-Host vom LANCOM-Router erhalten hat(Fest oder aus einem IP-Pool), schickt den Verkehr Richtung 192.168.0.0/24 über das VPN. Ohne diese Zeile wird keine Route gesetzt.
Es sind mehrfach Nennungen möglich. So könnte z.B. durch eine weitere Zeile wie "from dynamic to 192.168.1.2/32" ermöglicht werden, das der OpenBSD-Host mit dem Gerät 192.168.1.2, welches sich auf der LANCOM-Seite in einem anderen VLAN befindet, kommuniziert. Es darf natürlich den gesetzten
⎇IP- und Firewall-Regeln nicht widersprochen werden. Die LANCOM-Firewall behandelt einzelne VPN-Verbindungen als separate Gegenstellen. Wer per Firewall z.B. Traffic zwischen seinen verschiedenen LANs blockiert, muss für die betreffende VPN-Gegenstelle extra Freigaben erstellen. Obwohl in diesem Beispiel der OpenBSD-Host eine Adresse aus 192.168.0.0/24 erhält, kann er nicht automatisch mit allen anderen Hosts auf 192.168.0.0/24 kommunizieren. Er wird von der LANCOM-Firewall wie ein Netzfremder behandelt. Eben eine eigenen "Gegenstelle". (Wer nur die Out-of-the-box-Firewall-Regeln hat, muss nichts extra anpassen.)
“peer v4.vpn.example.com \” IPv4-Adresse oder Domain des LANCOM-Routers. Wichtig: Die Domain darf nur einen A-Record haben! Grundsätzlich funktioniert zwar IPv6, aber leider ist es auf Seiten von iked instabil. Erstens
⎇muss per local eine IPv6-Adress genannt werden. Zweitens kann es bei einer zeitlich engen Neuverbindung vorkommen, das die VPN-Verbindung als vollständig aufgebaut angezeigt wird, aber keine Daten fließen. Das passiert mit IPv4 nicht. Ein weiter Bug scheint zu sein, das IPv6 erzwungen wird, sobald die Domain einen AAAA-Record hat. Unabhängig davon ob per inet oder local IPv4 impliziert wird. Es kommt dann zu einer "create_ike: policy address family mismatch" Meldung.
[2] Update
“childsa enc aes-256-gcm group modp2048 \”
[1] Update beachten! Ohne diese Zeile läuft zunächst alles normal. Bis plötzlich bei einer Child SA Schlüsselneuaushandlung die Verbindung mit "ikev2_resp_create_child_sa: no proposal chosen" unterbrochen wird. Es kann bei wenig Datenverkehr durchaus mehrere Stunden dauern, bis der Fall auftritt. Die VPN-Verbindung wird nach ein paar Sekunden zwar neu aufgebaut, aber alle laufenden TCP-Verbindungen werden natürlich unterbrochen.
“srcid Open@BSD \” Identität des Clients. Muss dem Schema einer E-Mailadresse entsprechen und auf LANCOM-Seite mit der Entfernten Identität übereinstimmen.
“ikelifetime 54000 \”
“lifetime 14400 bytes 1G \” Beim Überschreiten der IKE und Child SA Gültigkeitswerte (für Zeit oder Verkehr) erfolgt eine Schlüsselneuaushandlung. Wird diese Neuaushandlung von Seiten des Routers initialisiert, scheitert der Vorgang, vermutlich wegen Interoperabilitätsproblemen, mit einem IKE-I-General-failure 0x21ff. Wird das Rekeying allerdings von iked angestoßen, funktioniert es problemlos.
Bei den hier angegebenen Werten handelt es sich um halbierte LANCOM-Werte. Das stellt sicher, dass das Rekeying immer von iked ausgeht.
“psk "f00b4r" \” ist das Shared Secret, der Preshared Key.
“request address any \” Der Client bezieht seine Netzwerkkonfiguration (IP-Adresse, DNS) vom VPN-Router.
“iface lo1” Welcher freie Loopback-Netzwerkadapter für das VPN verwendet werden soll. Der Adapter muss vor dem starten von iked existieren. "ifconfig lo1 create" erzeugt ihn. Damit er bei einem Neustart automatisch angelegt wird, muss /etc/hostname.lo1 existieren. Eine leere Datei mit dem Namen genügt.
Authentifizierung mit Zertifikat
Wie man eine CA erstellt und den LANCOM-Router dafür konfiguriert, habe ich in meinem OpenSSL-Spickzettel beschrieben:
⍈openssl.txt
⍈openssl_Beispiel.cnf
Einfach dem Beispiel "Android mit strongSwan" folgen. Der Weg ist mehr oder weniger der Gleiche. Den PKCS12-Container braucht man nicht.
# cp example.com_CACert.pem /etc/iked/ca/
# cp bsd.example.local_Cert.pem /etc/iked/certs/
# mv /etc/iked/private/local.key /etc/iked/private/local.key-install
# cp bsd.example.local_Key.pem /etc/iked/private/local.key
# chmod 600 /etc/iked/private/local.key
# cat /etc/iked.conf
set dpd_check_interval 30
ikev2 "Lancom" active \
from 192.168.0.0/24 to any \
from dynamic to 192.168.0.0/24 \
peer v4.vpn.example.com \
childsa enc aes-256-gcm group modp2048 \ [1]
srcid "/C=DE/ST=Bavaria/O=example.com/OU=Example.com Certification
Authority Facility/CN=bsd.example.local/emailAddress=bsd@example.local" \
dstid "/C=DE/ST=Bavaria/O=example.com/OU=Example.com Certification
Authority Facility/CN=vpn.example.com/emailAddress=vpn@example.com" \
ikelifetime 54000 \
lifetime 14400 bytes 1G \
request address any \
iface lo1
#eof
“srcid "/..." \” Hier ist der vollständigen Betreff (ASN1_DN / ASN.1-Distinguished-Name) des Client-Zertifikats nötig. Dieser kann wie folgt ermittelt werden:
# openssl x509 -subject -noout -in bsd.example.local_Cert.pem
subject= /C=DE/ST=Bavaria/O=example.com/OU=Example.com Certification Authority Facility/CN=bsd.example.local/emailAddress=bsd@example.local
“dstid "/..." \” Vollständiger DN des Routers.
# openssl x509 -subject -noout -in vpn.example.com_Cert.pem
subject= /C=DE/ST=Bavaria/O=example.com/OU=Example.com Certification Authority Facility/CN=vpn.example.com/emailAddress=vpn@example.com
Fertig
BTW:
- Die Reihenfolge der Parameter in der Config-Datei ist wichtig. Sie können nicht beliebig aufgeführt werden. In der Regel passt die Reihenfolge, wie sie in der
⎇manpage auftauchen.
- Der VPN-Router wird per CFG immer einen DNS-Server mitteilen, der bei einer OpenBSD-Standardkonfiguration mit laufendem resolvd zwangsläufig in die /etc/resolv.conf geschrieben wird. In meinem Setup möchte ich nicht, das DNS-Anfragen übers VPN laufen. Dazu gibt es zwei Möglichkeiten: resolvd deaktivieren. "rcctl stop resolvd" / "resolvd_flags=NO" in /etc/rc.conf.local (Dadurch erhält man eine klassische statische /etc/resolv.conf, die dann natürlich auch nicht von DHCP angefasst wird.)
Oder als Workaround auf dem LANCOM, für die Verbindung, einen eigenen Adress-Pool konfigurieren und darin einen gewünschten DNS-Server anzugeben.
- Der OpenBSD-Host kann mit seiner eigenen VPN-IP nicht kommunizieren. Wer also z.B. gerne einen ping auf sich selber macht, um zu testen ob das VPN steht, wird das nicht als Signal verwenden können. Auf der LANCOM-Firewall ist zu sehen wie der Verkehr aufgrund von "DoS protection" verworfen wird. Warum der lokale Verkehr überhaupt ins VPN geworfen wird ist mir nicht klar.
[1] Update 2024-03-22:
Seit einiger Zeit, und ich habe leider nicht aufgepasst um zu sagen seit wann oder durch welches Update (von LCOS oder OpenIKED), ist der childsa-Parameter nicht mehr nötig. Ja gar im Gegenteil, auf Systemen mit LCOS 10.80 und OpenIKED 7.3 sorgt der Parameter, in seiner hier angegebenen Form, für das was er einmal verhindern sollte. (Verbindungsabbruch beim erreichen der lifetime-Werte). Auf aktuellen Systemen ab sofort weglassen!
[2] Update 2024-12-06:
Mittlerweile (OpenBSD 7.6, LCOS 10.80) geht IPv6 als Transportprotokoll, auch wenn leider immer noch zusätzliche Workarounds nötig sind.
# cat /etc/iked.conf
set nomobike
set dpd_check_interval 30
ikev2...
local ipv6-client.example.com peer vpn.example.com \
...
"set nomobike" muss gesetzt sein.
Mit local gibt man die ausgehend zu verwendende IPv6-Adresse an. Entweder direkt als Adresse oder auch als Hostname. (Das man das machen muss ist wirklich sehr komisch. Es kommt zwar nicht mehr wie früher zu einer “address family mismatch” Fehlermeldung, wenn man den local-Teil wegläßt, aber auf LANCOM-Seite wird von "NAT detection: Remote gateway" berichtet, ohne das eine UDP-Encapsulation verwendet wird. Im Tunnel fließen dann keine Daten.)
Bei festen IPs ist das ganze ja kein Problem. Hinter einem Konsumer-Zugang oder bei mobilen Lösungen ist noch Mehrarbeit nötig. (Hinter einer Fritz!Box könnte man z.B. auf eine MyFRITZ!-Adresse zurückgreifen. (Wer eine auf MyFRITZ basierende Portweiterleitung aktiviert, erhält eine zum Host passende Subdomain.))
Ich verwende dieses Script
⍈IPv6Aliases-de.sh zum erkennen von Änderungen des IPv6-Netztes. Die change()-Funktion ist um folgende Zeilen erweitert:
...
ifconfig $interface inet6 alias $newIPv6Net::56:50:4e
sleep 1
sed -iIPv6Aliases "s/.*ipv6-client.internal/$newIPv6Net::56:50:4e ipv6-client.internal/g" /etc/hosts
rcctl restart iked
# Alte Adressen ablöschen
if [ "$oldIPv6Net" != "" ]
then
ifconfig $interface inet6 delete $oldIPv6Net::56:50:4e
fi
....
In der /etc/hosts gibt es einen entsprechenden Eintrag für ipv6-client.internal.
⍈Homepage